//! Utility for actually assembling chunks into Godot objects.

use std::sync::Arc;

use craftdig_core::registrar::GameRegistrar;
use craftdig_datamodel::world::WorldView;
use craftdig_math::{ChunkCoord, SubchunkPos};

use godot::classes::{Material, Mesh, MeshInstance3D};
use godot::prelude::*;
use thiserror::Error;

use crate::chunk_render;

#[derive(Error, Debug)]
pub enum Error {
    #[error("world missing chunk {0:?}")]
    MissingChunk(ChunkCoord),
}

pub struct ChunkBuilder {
    registrar: Arc<GameRegistrar>,
    block_mat: Gd<Material>,
}

impl ChunkBuilder {
    pub fn new(registrar: Arc<GameRegistrar>, block_mat: Gd<Material>) -> Self {
        Self {
            registrar,
            block_mat,
        }
    }

    pub fn build_chunk_cont(
        &self,
        coord: ChunkCoord,
        world: &impl WorldView,
    ) -> Result<Gd<Node3D>, Error> {
        // Look up the chunk data.
        let Some(chdata) = world.get_chunk_ref(coord) else {
            godot_warn!("craftdig: called update_chunk_node for missing chunk, skipping");
            return Err(Error::MissingChunk(coord));
        };

        let mut new_chunk_cont = Node3D::new_alloc();

        let mut non_air_blocks = 0;

        // This loop *was* used to create Godot colliders, but we don't need
        // those anymore so this just counts the non-air blocks.
        for sc_pos in SubchunkPos::iter() {
            let blkid = chdata.get_block_id(sc_pos);

            // Special case air, don't think about it.
            if blkid.is_zero() {
                continue;
            }

            non_air_blocks += 1;
        }

        // Only generate a mesh for a chunk if it has blocks to render.
        if non_air_blocks > 0 {
            // Generate the chunk mesh, add it to a mesh instance, and insert it
            // into the tree under the chunk node.
            let chunk_mesh = chunk_render::gen_chunk_mesh(coord, world, self.registrar.as_ref());
            let mut mi_node = MeshInstance3D::new_alloc();
            mi_node.set_mesh(chunk_mesh.upcast::<Mesh>());
            mi_node.set_material_override(self.block_mat.clone());
            new_chunk_cont.add_child(mi_node.upcast::<Node>());
        }

        Ok(new_chunk_cont)
    }
}
